#region Using Directives
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Specialized;
using System.Web;
using System.Diagnostics;
using System.Security.Cryptography;
using System.IO;
using System.IO.Compression;
using System.Web.UI;
using System.Security.Permissions;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading; 
#endregion Using Directives

namespace Minimal.Web
{
    /// <summary>
    /// Provides a base class for strongly typed query strings.
    /// The query strings can also optionally be encrypted.
    /// </summary>
    [ReflectionPermission(SecurityAction.Demand)]
    public abstract partial class QueryString
        : ICloneable, IRedirectManager
    {
        #region Variables
        private static Dictionary<Type, Dictionary<string, ParameterHandler>> allHandlers 
            = new Dictionary<Type, Dictionary<string, ParameterHandler>>();
        private IRedirectManager redirectManager;
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a new instance.
        /// </summary>
        protected QueryString()
        {
            this.Initialize();
            this.ParameterExclusions = ParameterExclusions.None;
            this._encryptQueryString = QueryStringConfiguration.Instance.EncryptQueryString;
            this._includeMetadata = QueryStringConfiguration.Instance.IncludeMetadata;
            this.redirectManager = this;
        }
        #endregion

        #region Properties
        #region Handlers
        /// <summary>
        /// Gets the QueryStringParameterHandlers that handles the rendering
        /// and parsing of the query string parameters.
        /// </summary>
        private Dictionary<string, ParameterHandler> Handlers
        {
            get
            {
                return QueryString.GetParameterHandlers(this.GetType());
            }
        } 
        #endregion

        #region ParameterExclusions
        [DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
        private ParameterExclusions _parameterExclusions;

        /// <summary>
        /// Parameters to be excluded when rendering the query string.
        /// </summary>
        public ParameterExclusions ParameterExclusions
        {
            [DebuggerStepThroughAttribute]
            get
            {
                return this._parameterExclusions;
            }
            [DebuggerStepThroughAttribute]
            set
            {
                this._parameterExclusions = value;
            }
        }
        #endregion ParameterExclusions

        #region EncryptQueryString
        private bool _encryptQueryString;
        
        /// <summary>
        /// If set to true the query string will be encrypted when rendered.
        /// </summary>
        public bool EncryptQueryString
        {
            get
            {
                return this._encryptQueryString;
            }
            set
            {
                this._encryptQueryString = value;
            }
        }
        #endregion EncryptQueryString

        #region IncludeMetadata
        [DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
        private bool _includeMetadata;

        /// <summary>
        /// Whether metadata should be included in the rendered query string.
        /// </summary>
        public bool IncludeMetadata
        {
            [DebuggerStepThroughAttribute]
            get
            {
                return this._includeMetadata;
            }
            [DebuggerStepThroughAttribute]
            set
            {
                this._includeMetadata = value;
            }
        }
        #endregion IncludeMetadata

        #region EncryptedQueryStringKey
        /// <summary>
        /// The key to use for the encrypted query string value if
        /// encryption of the query string is turned on.
        /// </summary>
        private static string EncryptedQueryStringKey
        {
            [DebuggerStepThrough]
            get
            {
                return QueryStringConfiguration.Instance.EncryptedQueryStringKey;
            }
        } 
        #endregion

        #region ParseOptions
        private static ParseOptions _parseOptions = QueryStringConfiguration.Instance.ParseOptions;
        /// <summary>
        /// Parse options for QueryString.
        /// </summary>
        public static ParseOptions ParseOptions
        {
            [DebuggerStepThrough()]
            get { return _parseOptions; }
        }
        #endregion ParseOptions

        #region EncryptionKey
        /// <summary>
        /// Key to use when encrypting and decrypting query strings.
        /// </summary>
        private static string EncryptionKey
        {
            get
            {
                return QueryStringConfiguration.Instance.EncryptionKey;
            }
        }
        #endregion        

        #region Page
        [DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
        private Page _page;

        /// <summary>
        /// The page that this QueryString belongs to.
        /// </summary>
        public Page Page
        {
            [DebuggerStepThroughAttribute]
            get
            {
                return this._page;
            }
            [DebuggerStepThroughAttribute]
            set
            {
                this._page = value;
            }
        }
        #endregion Page

        #region MetadataKey
        /// <summary>
        /// A key to use for the query string type metadata in the query string.
        /// </summary>
        private static string MetadataKey
        {
            get
            {
                return QueryStringConfiguration.Instance.MetadataKey;
            }
        }
        #endregion MetadataKey
        #endregion Properties

        #region Methods
        #region Redirect
        /// <summary>
        /// Redirects to the page specified in the url parameter.
        /// </summary>
        /// <param name="url">The url to redirect to, relative or static url's are
        /// accepted.</param>
        /// <exception cref="NotSupportedException">The Control-property is not set.</exception>
        /// <exception cref="ArgumentException">An invalid url has been specified.</exception>
        [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads",
            Justification="The url my be relative therefore it can not be converted to the Uri-type.")]
        public void Redirect(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentException(QueryStringResources.ToStringEmptyUrlException);
            }

            this.DoRedirect(url);
        }

        /// <summary>
        /// Performs the redirect to the specified url.
        /// </summary>
        /// <param name="url">The url to redirect to.</param>
        private void DoRedirect(string url)
        {
            this.redirectManager.Redirect(this.ToString(url));
        }

        /// <summary>
        /// Redirects to the page specified in the url parameter.
        /// </summary>
        /// <param name="url">The url to redirect to.</param>
        /// <exception cref="NotSupportedException">The Control-property is not set.</exception>
        /// <exception cref="ArgumentException">An invalid url has been specified.</exception>
        public void Redirect(Uri url)
        {
            if (url == null)
            {
                throw new ArgumentNullException("url");
            }

            this.DoRedirect(url.AbsoluteUri);
        }
        #endregion Redirect

        #region Initialize
        private void Initialize()
        {
            foreach (ParameterHandler handler in this.Handlers.Values)
            {
                handler.SetDefaultValue(this);
            }
        }
        #endregion

        #region ToString
        /// <summary>
        /// Creates a string representation of the query string.
        /// </summary>
        /// <returns>A query string.</returns>
        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
            bool first = true;

            foreach (ParameterHandler handler in this.Handlers.Values)
            {
                string value = handler.GetStringValue(this);
                
                
                if (this.ShouldRender(handler))
                {
                    if (!first)
                    {
                        result.Append('&');
                    }
                    else
                    {
                        first = false;
                    }

                    result.Append(HttpUtility.UrlEncode(handler.Key));
                    result.Append('=');
                    result.Append(HttpUtility.UrlEncode(value));
                }
            }

            if (this.EncryptQueryString)
            {
                string qs = result.ToString();
                result = new StringBuilder();
                result.Append(HttpUtility.UrlEncode(QueryString.EncryptedQueryStringKey));
                result.Append('=');
                result.Append(HttpUtility.UrlEncode(QueryString.Encrypt(qs)));
            }

            this.AppendMetadata(result);
            
            return result.ToString();
        }

        /// <summary>
        /// Creates the query string and appends it to the given url.
        /// </summary>
        /// <param name="url">The url to append the query to, any existing query will be replaced.</param>
        /// <returns>The complete url including this query string.</returns>
        [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", 
            Justification="The url may be relative, therefore it can not be conveterd to the Uri-type.")]
        public string ToString(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentException(QueryStringResources.ToStringEmptyUrlException, "url");
            }

            return QueryString.CombineUrlAndQuery(url, this.ToString());
        }

        /// <summary>
        /// Creates the query string and appends it to the given url.
        /// </summary>
        /// <param name="url">The url to append the query to, any existing query will be replaced.</param>
        /// <returns>The complete url including this query string.</returns>
        public string ToString(Uri url)
        {
            if (url == null)
            {
                throw new ArgumentNullException("url");
            }

            return QueryString.CombineUrlAndQuery(url.AbsoluteUri, this.ToString());
        }
        #endregion

        #region CombineUrlAndQuery
        private static string CombineUrlAndQuery(string url, string query)
        {
            return (string.IsNullOrEmpty(query)) ? url : url + '?' + query;
        }
        #endregion

        #region ShouldRender
        /// <summary>
        /// Determines if the handler should render according to the current settings.
        /// </summary>
        private bool ShouldRender(ParameterHandler handler)
        {
            if ((this.ParameterExclusions & ParameterExclusions.ExcludeDefaultValues) == ParameterExclusions.ExcludeDefaultValues && handler.HasDefaultValue(this))
            {
                return false;
            }
            else if ((this.ParameterExclusions & ParameterExclusions.ExcludeEmptyDefaultValues) == ParameterExclusions.ExcludeEmptyDefaultValues && handler.DefaultValueIsEmpty)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        #endregion ShouldRender

        #region AppendMetadata
        /// <summary>
        /// Appends metadata to the supplied StringBuilder that
        /// should contain a query string.
        /// </summary>
        /// <param name="result">The StringBuilder to append the
        /// metadata string to.</param>
        private void AppendMetadata(StringBuilder result)
        {
            if (this._includeMetadata)
            {
                if (result.Length > 0)
                {
                    result.Append('&');
                }

                result.Append(HttpUtility.UrlEncode(QueryString.MetadataKey));
                result.Append('=');
                result.Append(HttpUtility.UrlEncode(QueryString.Encrypt(
                    new QueryStringMetadata(this.GetType()).ToString())));
            }
        } 
        #endregion

        #region GetParameterHandlers
        /// <summary>
        /// Gets the QueryStringParameterHandlers that handles the
        /// QueryString's of the given subtype.
        /// </summary>
        /// <param name="t">The type of QueryString to get the handlers
        /// for.</param>
        /// <returns>A dictionary of QueryStringParameterHandlers.</returns>
        /// <exception cref="ReservedKeyException">Thrown if a handler has a key specified
        /// that is reserved by the system.</exception>
        private static Dictionary<string, ParameterHandler> GetParameterHandlers(Type t)
        {
            Dictionary<string, ParameterHandler> result;
            
            lock (QueryString.allHandlers)
            {
                if (!QueryString.allHandlers.TryGetValue(t, out result))
                {
                    result = new Dictionary<string, ParameterHandler>();
                    foreach (MemberInfo i in t.GetMembers(
                        BindingFlags.GetProperty | BindingFlags.GetField |
                        BindingFlags.NonPublic | BindingFlags.Public |
                        BindingFlags.Instance))
                    {
                        QueryStringParameterAttribute[] a =
                            (QueryStringParameterAttribute[])i.GetCustomAttributes(
                            typeof(QueryStringParameterAttribute), true);

                        if (a.Length > 0)
                        {
                            QueryString.ValidateParameterKey(a[0].Key);
                            result.Add(a[0].Key.ToLower(CultureInfo.InvariantCulture), ParameterHandler.Create(a[0], i));
                        }
                    }
                    QueryString.allHandlers.Add(t, result);
                }
            }
            
            return result;
        }

        /// <summary>
        /// Validates that the specified key can be used as a parameter key.
        /// </summary>
        private static void ValidateParameterKey(string p)
        {
            if (string.Equals(QueryString.EncryptedQueryStringKey, p, StringComparison.InvariantCultureIgnoreCase) ||
                string.Equals(QueryString.MetadataKey, p, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new ReservedKeyException(p);
            }
        }
        #endregion GetParameterHandlers

        #region Parse
        /// <summary>
        /// Parse the supplied query string into a QueryString object.
        /// </summary>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <param name="queryString">The query string to parse.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(string queryString) where T : QueryString, new()
        {
            return QueryString.Parse<T>(queryString, null);
        }

        /// <summary>
        /// Parse the supplied query string into a QueryString object.
        /// </summary>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="control">The control or page the query string belongs to.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(string queryString, Control control) where T : QueryString, new()
        {
            T result = new T();
            if (control != null)
            {
                result.Page = control.Page;
            }
            QueryString.PrivateParse(queryString, result);
            return result;
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object.
        /// </summary>
        /// <param name="queryString">The query string to parse in the form used
        /// by the Request.QueryString-property.</param>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(NameValueCollection queryString) where T : QueryString, new()
        {
            return QueryString.Parse<T>(queryString, null);
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object.
        /// </summary>
        /// <param name="queryString">The query string to parse in the form used
        /// by the Request.QueryString-property.</param>
        /// <param name="control">The Page or Control the query string belongs to.</param>
        /// <typeparam name="T">The type of QueryString to parse, a sub class of the
        /// abstract QueryString base class.</typeparam>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static T Parse<T>(NameValueCollection queryString, Control control) where T : QueryString, new()
        {
            T result = new T();
            if (control != null)
            {
                result.Page = control.Page;
            }
            QueryString.PrivateParse(queryString, result);
            return result;
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object using
        /// the query strings metadata to decide the type of QueryString to parse.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static QueryString Parse(string queryString)
        {
            return QueryString.Parse(queryString, null);
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object using
        /// the query strings metadata to decide the type of QueryString to parse.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="control">The Page or Control the query string belongs to.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static QueryString Parse(string queryString, Control control)
        {
            string metadata = QueryString.ExtractMetadataString(queryString);
            QueryString result = QueryString.CreateInstanceFromMetadata(metadata);
            if (control != null)
            {
                result.Page = control.Page;
            } 
            QueryString.PrivateParse(queryString, result);
            return result;
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object using
        /// the query strings metadata to decide the type of QueryString to parse.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static QueryString Parse(NameValueCollection queryString)
        {
            return QueryString.Parse(queryString, null);
        }

        /// <summary>
        /// Parses the supplied query string into a QueryString object using
        /// the query strings metadata to decide the type of QueryString to parse.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="control">The Page or Control the query string belongs to.</param>
        /// <returns>A QueryString object.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
        public static QueryString Parse(NameValueCollection queryString, Control control)
        {
            if (queryString == null)
            {
                throw new ArgumentNullException("queryString");
            }
            
            string metadata = queryString[QueryString.MetadataKey];
            QueryString result = QueryString.CreateInstanceFromMetadata(metadata);
            if (control != null)
            {
                result.Page = control.Page;
            } 
            QueryString.PrivateParse(queryString, result);
            return result;
        }
        #endregion

        #region ExtractMetadataString
        private static string ExtractMetadataString(string queryString)
        {
            int startIndex = queryString.IndexOf("&" + QueryString.MetadataKey);
            if (startIndex == -1)
            {
                startIndex = queryString.IndexOf("?" + QueryString.MetadataKey);
            }

            if (startIndex == -1)
            {
                return null;
            }

            startIndex += QueryString.MetadataKey.Length + 2;
            return HttpUtility.UrlDecode(queryString.Substring(startIndex, queryString.Length - startIndex));
        }
        #endregion ExtractMetadataString

        #region CreateInstanceFromMetadata
        /// <summary>
        /// Uses the metadata of a query string to create an instance
        /// of the correct QueryString sub type.
        /// </summary>
        /// <param name="metadata">The metadata to use.</param>
        /// <returns>A QueryString instance.</returns>
        private static QueryString CreateInstanceFromMetadata(string metadata)
        {
            if (string.IsNullOrEmpty(metadata))
            {
                throw new QueryStringParseException(QueryStringResources.ParseMissingMetadataException);
            }

            QueryStringMetadata m = QueryStringMetadata.Parse(QueryString.Decrypt(metadata));

            QueryString result;

            try
            {
                result = (QueryString)Activator.CreateInstance(m.QueryStringType, true);
            }
            catch (Exception ex)
            {
                throw new QueryStringParseException(
                    QueryStringResources.ParseUnkownQueryStringTypeException, ex);
            }

            return result;
        }
        #endregion CreateInstanceFromMetadata

        #region PrivateParse
        /// <summary>
        /// Parses the specified query string applying its values to the QueryString
        /// specified in the <paramref name="result"/> parameter.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The QueryString to apply the parsed values to.</param>
        private static void PrivateParse(NameValueCollection queryString, QueryString result)
        {
            bool isEncrypted = QueryString.IsEncrypted(queryString);

            if (queryString.Count > 0 
                && (QueryString.ParseOptions & ParseOptions.ParseEncryptedOnly) == ParseOptions.ParseEncryptedOnly 
                && !isEncrypted)
            {
                throw new QueryStringParseException(QueryStringResources.ParseEncryptedOnlyException);
            }

            if (isEncrypted)
            {
                QueryString.PrivateParseEncrypted(queryString, result);
            }
            else
            {
                QueryString.PrivateParseNonEncrypted(queryString, result);
            }
        }

        /// <summary>
        /// Parses the specified query string, applying the values to the
        /// <paramref name="result"/> <see cref="QueryString" />-instance.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        private static void PrivateParse(string queryString, QueryString result)
        {
            queryString = QueryString.StripMetadata(queryString);
            bool isEncrypted = QueryString.IsEncrypted(queryString);

            if (!string.IsNullOrEmpty(queryString)
                && (QueryString.ParseOptions & ParseOptions.ParseEncryptedOnly) == ParseOptions.ParseEncryptedOnly
                && !isEncrypted)
            {
                throw new NotSupportedException(QueryStringResources.ParseEncryptedOnlyException);
            }

            if (isEncrypted)
            {
                QueryString.PrivateParseEncrypted(queryString, result);
            }
            else
            {
                QueryString.PrivateParseNonEncrypted(queryString, result);
            }
        }
        #endregion PrivateParse

        #region DoParse
        /// <summary>
        /// Parses a collection of key value pairs to a QueryString object,
        /// the keys and values should not be url-encoded, used by the public
        /// parse methods.
        /// </summary>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        /// <param name="parameters">The parameters to parse.</param>
        private static void DoParse(IEnumerable<KeyValuePair<string, string>> parameters, QueryString result)
        {
            Dictionary<string, ParameterHandler> handlers = QueryString.GetParameterHandlers(result.GetType());
            int handledParameters = 0;

            foreach (KeyValuePair<string, string> pair in parameters)
            {
                ParameterHandler handler;
                if (handlers.TryGetValue(pair.Key.ToLower(CultureInfo.InvariantCulture), out handler))
                {
                    handler.SetValueFromString(result, pair.Value);
                    handledParameters++;
                }
                else
                {
                    if (!((QueryString.ParseOptions & ParseOptions.AllowAdditionalParameters) == ParseOptions.AllowAdditionalParameters))
                    {
                        throw new QueryStringParseException(string.Format(
                            CultureInfo.InvariantCulture,
                            QueryStringResources.ParseParameterNotPartOfQueryException,
                            pair.Key, result.GetType().FullName));
                    }
                }
            }

            if ((QueryString.ParseOptions & ParseOptions.RequireAllParameters) == ParseOptions.RequireAllParameters
                && handledParameters < handlers.Count)
            {
                throw new QueryStringParseException(QueryStringResources.ParseRequireAllNotAllSuppliedException);
            }
        }
        #endregion

        #region PrivateParseEncrypted
        /// <summary>
        /// Parses an encrypted query string.
        /// </summary>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        /// <param name="queryString">The query string to parse.</param>
        /// <returns>A QueryString object.</returns>
        private static void PrivateParseEncrypted(NameValueCollection queryString, QueryString result)
        {
            Debug.WriteLine(queryString[QueryString.EncryptedQueryStringKey]);
            QueryString.PrivateParseNonEncrypted(QueryString.Decrypt(queryString[QueryString.EncryptedQueryStringKey]), result);
        }

        /// <summary>
        /// Parses an encrypted query string.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        /// <returns>A QueryString object.</returns>
        private static void PrivateParseEncrypted(string queryString, QueryString result)
        {
            QueryString.PrivateParseNonEncrypted(QueryString.Decrypt(HttpUtility.UrlDecode(queryString.Split('=')[1])), result);
        }
        #endregion PrivateParseEncrypted

        #region PrivateParseNonEncrypted
        /// <summary>
        /// Parses a non encrypted query string.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        /// <returns>A QueryString object.</returns>
        private static void PrivateParseNonEncrypted(NameValueCollection queryString, QueryString result)
        {
            Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();

            foreach (string key in queryString)
            {
                if (!string.Equals(QueryString.MetadataKey, key, StringComparison.InvariantCultureIgnoreCase))
                    keyValuePairs.Add(key, queryString[key]);
            }

            QueryString.DoParse(keyValuePairs, result);
        }

        /// <summary>
        /// Parses a non encrypted query string.
        /// </summary>
        /// <param name="queryString">The query string to parse.</param>
        /// <param name="result">The instance that the parsed values should be
        /// applied to.</param>
        /// <returns>A QueryString object.</returns>
        private static void PrivateParseNonEncrypted(string queryString, QueryString result)
        {
            List<KeyValuePair<string, string>> keyValuePairs = new List<KeyValuePair<string, string>>();
            if (!string.IsNullOrEmpty(queryString))
            {
                string[] keysAndValues = queryString.Split('&');
                foreach (string keyAndValue in keysAndValues)
                {
                    string[] keyValue = keyAndValue.Split('=');
                    keyValuePairs.Add(new KeyValuePair<string, string>(HttpUtility.UrlDecode(keyValue[0]), HttpUtility.UrlDecode(keyValue[1])));
                }    
            }

            QueryString.DoParse(keyValuePairs, result);
        }
        #endregion PrivateParseNonEncrypted

        #region StripMetadata
        /// <summary>
        /// Removes the metadata from the string and returns the rest.
        /// </summary>
        /// <param name="queryString">The query string to remove the metadata from.</param>
        /// <returns>The query string without the metadata.</returns>
        private static string StripMetadata(string queryString)
        {
            if (string.IsNullOrEmpty(queryString))
            {
                return queryString;
            }
            else
            {
                int startIndex = queryString.IndexOf("&" + QueryString.MetadataKey);
                startIndex = (startIndex == -1) ? queryString.IndexOf("?" + QueryString.MetadataKey) : startIndex;
                return (startIndex != - 1) ? 
                    queryString.Substring(0, startIndex) :
                    queryString;
            }
        }
        #endregion StripMetadata

        #region IsEncrypted
        /// <summary>
        /// Gets if the specified query string has been encrypted or not.
        /// </summary>
        /// <param name="queryString">The query string to test.</param>
        /// <returns>True if the query string was encrypted.</returns>
        private static bool IsEncrypted(string queryString)
        {
            return string.IsNullOrEmpty(queryString) || 
                queryString.StartsWith(QueryString.EncryptedQueryStringKey);
        }

        /// <summary>
        /// Gets if the specified query string has been encrypted or not.
        /// </summary>
        /// <param name="queryString">The query string to test.</param>
        /// <returns>True if the query string was encrypted.</returns>
        private static bool IsEncrypted(NameValueCollection queryString)
        {
            return queryString[QueryString.EncryptedQueryStringKey] != null;
        }
        #endregion IsEncrypted

        #region Decrypt
        /// <summary>
        /// Decrypts the supplied string.
        /// </summary>
        /// <param name="queryString">The string to decrypt.</param>
        /// <param name="factory">Factory to create a compression stream, used to decompress
        /// the encrypted data.</param>
        /// <returns>The decrypted string.</returns>
        private static string Decrypt(string queryString, CompressionStreamFactory factory)
        {
            byte[] inputByteArray = null;
            try
            {
                inputByteArray = Convert.FromBase64String(queryString);
            }
            catch (FormatException ex)
            {
                QueryString.ThrowAlteredStringException(ex);
            }

            using (MemoryStream stream = new MemoryStream())
            using (CryptoStream cryptoStream = new CryptoStream(stream, QueryString.SymmetricAlgorithmFactory.GetAlgorithm(
                QueryString.EncryptionKey).CreateDecryptor(), CryptoStreamMode.Write))
            using (Stream compressionStream = factory(stream, CompressionMode.Decompress))
            {
                cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
                cryptoStream.FlushFinalBlock();
                stream.Position = 0;

                List<byte> buffer = new List<byte>();

                int currentByte = compressionStream.ReadByte();
                while (currentByte != -1)
                {
                    buffer.Add((byte)currentByte);
                    currentByte = compressionStream.ReadByte();
                }

                compressionStream.Close();
                cryptoStream.Close();
                stream.Close();

                return Encoding.Unicode.GetString(buffer.ToArray());
            }
        }

        /// <summary>
        /// Decrypts the supplied string.
        /// </summary>
        /// <param name="queryString">The string to decrypt.</param>
        /// <returns>The decrypted string.</returns>
        private static string Decrypt(string queryString)
        {
            return QueryString.Decrypt(queryString, delegate(Stream stream, CompressionMode mode)
            {
                return new DeflateStream(stream, mode);
            });
        }
        #endregion Decrypt

        #region SymmetricAlgorithmFactory
        /// <summary>
        /// The factory that creates the encryption algorithm.
        /// </summary>
        private static ISymmetricAlgorithmFactory SymmetricAlgorithmFactory
        {
            get
            {
                return QueryStringConfiguration.Instance.SymmetricAlgorithmFactory;
            }
        }
        #endregion SymmetricAlgorithmFactory

        #region CompressionStreamFactory
        /// <summary>
        /// Creates a compression stream working on the specified stream and mode.
        /// </summary>
        /// <param name="stream">The stream to work on.</param>
        /// <param name="mode">The compression mode to use.</param>
        /// <returns>A compression <see cref="Stream" />.</returns>
        private delegate Stream CompressionStreamFactory(Stream stream, CompressionMode mode);
        #endregion CompressionStreamFactory

        #region Encrypt
        /// <summary>
        /// Encrypts the supplied string.
        /// </summary>
        /// <param name="queryString">The string to encrypt.</param>
        /// <returns>The encrypted string.</returns>
        private static string Encrypt(string queryString)
        {
            return QueryString.Encrypt(queryString, delegate(Stream stream, CompressionMode mode)
            {
                return new DeflateStream(stream, mode);
            });
        }

        /// <summary>
        /// Encrypts the supplied string.
        /// </summary>
        /// <param name="queryString">The string to encrypt.</param>
        /// <param name="factory">Factory that creates a compression stream to compress the encrypted data.</param>
        /// <returns>The encrypted string.</returns>
        private static string Encrypt(string queryString, CompressionStreamFactory factory)
        {
            using (SymmetricAlgorithm algorithm = QueryString.SymmetricAlgorithmFactory.GetAlgorithm(
                    QueryString.EncryptionKey))
            {
                return QueryString.Encrypt(queryString, algorithm, factory);
            }
        }

        /// <summary>
        /// Encrypts the supplied string.
        /// </summary>
        /// <param name="s">The string to encrypt.</param>
        /// <param name="algorithm">The algorithm that should be used to encrypt the string.</param>
        /// <param name="factory">Factory that creates a compression stream to compress the encrypted data.</param>
        /// <returns>The encrypted string.</returns>
        private static string Encrypt(string s, SymmetricAlgorithm algorithm, CompressionStreamFactory factory)
        {
            string result;
            byte[] inputByteArray = Encoding.Unicode.GetBytes(s);

            using (MemoryStream stream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(stream, algorithm.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    using (Stream compressionStream = factory(cryptoStream, CompressionMode.Compress))
                    {
                        compressionStream.Write(inputByteArray, 0, inputByteArray.Length);
                        compressionStream.Flush();
                    }

                    cryptoStream.Flush();
                }

                stream.Flush();
                result = Convert.ToBase64String(stream.ToArray());
            }

            return result;
        }
        #endregion

        #region ThrowAlteredStringException
        /// <summary>
        /// Throws an exception for when an encrypted query string has been altered
        /// so that it can not be decrypted.
        /// </summary>
        /// <param name="innerException">Any inner exception or null.</param>
        private static void ThrowAlteredStringException(Exception innerException)
        {
            throw new QueryStringParseException(QueryStringResources.AlteredStringException, innerException);
        } 
        #endregion

        #region Clone
        object ICloneable.Clone()
        {
            return this.Clone();
        }

        /// <summary>
        /// Creates a new instance with the same values as this instance.
        /// </summary>
        /// <returns>A clone of this instance.</returns>
        public QueryString Clone()
        {
            QueryString result = (QueryString)Activator.CreateInstance(this.GetType());
            result.EncryptQueryString = this.EncryptQueryString;
            result.IncludeMetadata = this.IncludeMetadata;
            result.Page = this.Page;
            result.ParameterExclusions = this.ParameterExclusions;

            foreach (ParameterHandler handler in this.Handlers.Values)
            {
                handler.SetValue(result, handler.GetValue(this));
            }

            return result;
        }
        #endregion

        #region Redirect
        /// <summary>
        /// Redirects to the specified uri.
        /// </summary>
        /// <param name="relativeUrl">The url to redirect to, may be an absolute url, a relative
        /// url or a virtual path.</param>
        void IRedirectManager.Redirect(string relativeUrl)
        {
            if (this.Page == null)
            {
                throw new NotSupportedException(QueryStringResources.RedirectWithNoPageException);
            }

            try
            {
                this.Page.Response.Redirect(relativeUrl, true);
            }
            catch (System.Threading.ThreadAbortException) { }
        } 
        #endregion Redirect
        #endregion
    }
}
